package com.github.gaojh.ioc.bean;

import cn.hutool.core.util.ReflectUtil;
import cn.hutool.core.util.StrUtil;
import com.github.gaojh.config.Environment;
import com.github.gaojh.ioc.IocUtil;
import com.github.gaojh.ioc.annotation.Autowired;
import com.github.gaojh.ioc.annotation.InjectBean;
import com.github.gaojh.ioc.annotation.Value;
import lombok.extern.slf4j.Slf4j;

import java.lang.reflect.*;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;

/**
 * @author gaojh
 * @since 2020/3/23
 */
@Slf4j
public abstract class AbstractInitializer implements Initializer {

    private ClassScanner classScanner;

    protected Object createBean(ClassInfo classInfo) throws Exception {
        String name = classInfo.getBeanName();
        if (IocUtil.getBean(name) != null) {
            return IocUtil.getBean(name);
        }

        Object object;
        if (classInfo.isConfigurationBean()) {
            Object configObject = createBean(classInfo.getConfigurationClass());
            Method method = classInfo.getMethod();
            if (method.getParameterCount() == 0) {
                object = method.invoke(configObject);
            } else {
                object = method.invoke(configObject, getParameters(method));
            }
            log.debug("加载Configuration Bean：{}", name);
            IocUtil.addBean(classInfo.getBeanName(), object);
        } else {
            Class<?> finalClass = classInfo.getBeanClass();
            Constructor<?>[] constructors = finalClass.getConstructors();

            if (constructors.length == 0) {
                object = finalClass.newInstance();
            } else {
                Constructor<?> constructor = getAutowriedContructor(finalClass);
                if (constructor.getParameterCount() == 0) {
                    object = finalClass.newInstance();
                } else {
                    object = constructor.newInstance(getParameters(constructor));
                }
            }

            Arrays.stream(finalClass.getDeclaredFields()).filter(field -> field.isAnnotationPresent(Value.class)).forEach(field -> setValueField(object, field));
            //还未file设值，先放入临时的map中，设置完成之后再移入正式的
            IocUtil.addBean(name, object);
            //设置Field
            // setFields(object);
            log.debug("加载Bean：{}", name);
        }
        return object;
    }

    /**
     * 对象实例化
     */
    protected Object createBean(Class<?> clazz) throws Exception {
        ClassInfo classInfo = classScanner.getClassInfo(clazz);
        if (classInfo == null) {
            throw new RuntimeException("未找到到该类，可能未加入IOC管理：" + clazz.getName());
        }

        return createBean(classInfo);
    }

    protected Object createBean(String beanName) throws Exception {
        ClassInfo classInfo = classScanner.getClassInfo(beanName);
        if (classInfo == null) {
            throw new RuntimeException("未找到到该类，可能未加入IOC管理：" + beanName);
        }

        return createBean(classInfo);
    }


    /**
     * 获取构造函数，思路如下
     * 1、先判断是否有Autowried的构造函数，一个，则返回，多个报错
     * 2、再判断是否有无参构造函数，有则返回，无则报错
     */
    private Constructor<?> getAutowriedContructor(Class<?> clazz) {
        Constructor<?>[] constructors = clazz.getDeclaredConstructors();

        List<Constructor<?>> list = Arrays.stream(clazz.getConstructors()).filter(constructor -> constructor.isAnnotationPresent(Autowired.class)).collect(Collectors.toList());
        if (list.size() == 0) {
            for (Constructor<?> constructor : constructors) {
                if (constructor.getParameterCount() == 0) {
                    return constructor;
                }
            }
            throw new RuntimeException(String.format("类[%s]需要一个无参构造函数，否则无法实例化！", clazz.getName()));
        } else if (list.size() == 1) {
            return list.get(0);
        } else {
            throw new RuntimeException(String.format("类[%s]只能存在一个使用@Autowried注解的构造方法！", clazz.getName()));
        }
    }

    /**
     * 实例化方法入参
     */
    protected <T extends Executable> Object[] getParameters(T t) throws Exception {
        Parameter[] parameters = t.getParameters();
        List<Object> values = new ArrayList<>(parameters.length);
        for (Parameter p : parameters) {
            InjectBean bean = p.getAnnotation(InjectBean.class);
            String parameterName = classScanner.getBeanName(p.getType());
            if (bean != null) {
                parameterName = bean.value();
            }
            Object parameterObject = IocUtil.getBean(parameterName);
            if (parameterObject == null) {
                parameterObject = createBean(parameterName);
            }
            values.add(parameterObject);
        }
        return values.toArray();
    }

    private void setValueField(Object object, Field field) {
        Value value = field.getAnnotation(Value.class);
        String valueStr = value.value();
        if (StrUtil.isBlank(valueStr)) {
            throw new RuntimeException(String.format("[%s]中的Field：%s无法设值！", object.getClass().getName(), field.getName()));
        } else if (!StrUtil.containsAny(valueStr, "${", "}")) {
            throw new RuntimeException(String.format("[%s]中的Field：%s表达式格式不正确！", object.getClass().getName(), field.getName()));
        } else {
            String key = StrUtil.subBetween(valueStr, "${", "}");
            ReflectUtil.setFieldValue(object, field, Environment.me().get(key, null));
        }
    }


    public abstract void doInit(ClassScanner classScanner) throws Exception;


    @Override
    public void init(ClassScanner classScanner) throws Exception {
        this.classScanner = classScanner;
        doInit(classScanner);
    }
}
